home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d18 / tpwspy.arc / TPWSPY.PAS < prev    next >
Pascal/Delphi Source File  |  1991-07-25  |  39KB  |  952 lines

  1. program TPWSpy;
  2. (*
  3.   Program:            TPWPSY.PAS
  4.   Version:            1.0
  5.   Date:               July 26, 1991
  6.   Operating System:   MS-DOS 3.0 or greater
  7.                       Windows 3.0 or greater
  8.   Programming System: Turbo Pascal for Windows 1.0
  9.   Author:             Translation by Craig Boyd
  10.                       Based on SPY.C by Michael Geary
  11.  
  12.   Update History
  13.  
  14.   update    ver   description (author)
  15.   -------   ---   -----------
  16.   9107.18   0.0   Work begun. (CSB)
  17.   9107.26   1.0   Released to public domain. (CSB)
  18.  
  19.  
  20.   Description:
  21.  
  22.   A TPW version of SPY.C.  This version uses the Windows API and does not
  23.   incorporate ObjectWindows.  In other words, I did it the hard way!
  24.  
  25.   This program is based on the version of Spy described in an article by
  26.   Michael Geary which appeared in the 1987 All-IBM issue of Byte magazine.
  27.   The source code to this version of Spy was originally downloaded from
  28.   the BYTEnet BBS.  As far as I can tell, Mr. Geary wrote Spy for his own
  29.   use, and Microsoft later altered the program and added it to the Windows
  30.   Software Development Kit.  Since Michael released his version of the
  31.   program to the public domain, I see no problem with making my own
  32.   version and distributing it as I please.  The version of Spy that
  33.   currently ships with the SDK may be quite different (I've never seen
  34.   it), but some may find this version of interest.  Do with it what you
  35.   will.
  36.  
  37.   This translation is functionally identical to the original version.  The
  38.   only thing I've added is a Font menu, so you can play with the look of
  39.   the output a little bit.  TPWSpy defaults to the System Fixed font,
  40.   which is the same as the system font used in Windows 2.x.  You can
  41.   choose between that and the OEM fixed font and the System variable font.
  42.  
  43.   I've left in all of Mr. Geary's original comments, although some text
  44.   has been altered to reflect the change from C to Pascal.  There's a lot
  45.   of useful information here, especially the bit about eating wm_size
  46.   messages until the program is fully initialized.
  47.  
  48.   Thanks to J. W. Rider, Pat Ritchey, and Richard R. Sands for helping me
  49.   get a handle on some difficult to grasp (at least to me) TPW concepts
  50.   and for helping me figure out how to translate some of the more esoteric
  51.   C algorithms into Pascal.
  52.  
  53.   Craig Boyd
  54.   July, 1991
  55.  
  56.   -------------------------------------------------------------------------
  57.  
  58.   Windows Spy Program
  59.   Public Domain
  60.   Written by Michael Geary
  61.  
  62.   This program "spies" on all the windows that are currently open in your
  63.   Windows session, and displays a window containing all the information it
  64.   can find out about those windows.  You can scroll through this window
  65.   using either the mouse or keyboard to view the information about the
  66.   various windows.  The "New Spy Mission" menu item re-captures the latest
  67.   information.  This menu item is on the System menu so you can trigger it
  68.   even if the Spy window is iconic.  (Translator's note: no it isn't!)
  69.  
  70.   The display for a single window looks like this in collapsed mode:
  71.  
  72.   {Child|Popup|TopLevel} window HHHH {class} (L,T;R,B) "title"
  73.  
  74.   or like this in expanded mode:
  75.  
  76.       {Child|Popup|TopLevel} window handle: HHHH
  77.         Class name: {class name}
  78.         Window title: {title text}
  79.         Parent window handle: HHHH
  80.         Class function, window function: HHHH:HHHH, HHHH:HHHH
  81.         Class module handle, Window instance handle: HHHH, HHHH
  82.         Class extra alloc, Window extra alloc: DDDD, DDDD
  83.         Class style, Window style: HHHH, HHHHHHHH
  84.         Menu handle: HHHH   -or-  Control ID: DDDD
  85.         Brush, Cursor, Icon handles: HHHH, HHHH, HHHH
  86.         Window rectangle: Left=DDDD, Top=DDDD, Right=DDDD, Bottom=DDDD
  87.         Client rectangle: Left=DDDD, Top=DDDD, Right=DDDD, Bottom=DDDD
  88.       {blank line}
  89.  
  90.   Total number of lines for one window display: 13
  91. *)
  92.  
  93. {R-}   { hint: keep range checking on until your code is FULLY debugged! }
  94.  
  95. {$R TPWSPY}                                              { resource file }
  96.  
  97. uses WinTypes, WinProcs;
  98.  
  99. const
  100.   Cmd_Spy             = 101;                              { menu command }
  101.   Cmd_Expand          = 102;                              { menu command }
  102.   Cmd_OEMFixedFont    = 103;                              { menu command }
  103.   Cmd_SystemFixedFont = 104;                              { menu command }
  104.   Cmd_SystemFont      = 105;                              { menu command }
  105.   Ids_Class           = 1;                             { string resource }
  106.   Ids_Title           = 2;                             { string resource }
  107.   MaxLinesPerWin      = 13;
  108.   WindowWidth         = 120;
  109.   vk_MinCursor        = vk_prior;
  110.   vk_MaxCursor        = vk_down;
  111.   ClassMax            = 30;
  112.   TitleMax            = 50;
  113.   Initted             : bool = false;            { TRUE when initialized }
  114.   bExpand             : bool = false;           { Expanded display mode? }
  115.   LinesPerWin         : integer = 1;               { 1 or MaxLinesPerWin }
  116.   FontTable           : array[Cmd_OEMFixedFont..Cmd_SystemFont] of integer = (
  117.                           OEM_Fixed_Font,
  118.                           System_Fixed_Font,
  119.                           System_Font);
  120.   DefaultFont         = System_Fixed_Font;                { default font }
  121.   DefaultFontCmd      = Cmd_SystemFixedFont;      { default font command }
  122.   SpyFont             : integer = DefaultFont;            { current font }
  123.   SpyFontCmd          : word = DefaultFontCmd;    { current font command }
  124.  
  125. type
  126. {
  127.   The INFO record contains all the information we gather up about each
  128.   window we are spying on.  We allocate an array of INFO records in the
  129.   global heap, with one entry for each window in the system.
  130. }
  131.   Info = record
  132.     winHWnd        : hwnd;                               { Window handle }
  133.     winClass       : array[0..ClassMax] of char;            { Class name }
  134.     winBkgdBrush   : hbrush;                   { Background brush handle }
  135.     winCursor      : hcursor;                            { Cursor handle }
  136.     winIcon        : hicon;                                { Icon handle }
  137.     winClassModule : thandle;         { Module handle for owner of class }
  138.     winWndExtra    : word;        { Extra data allocated for each window }
  139.     winClsExtra    : word;        { Extra data allocated in class itself }
  140.     winClassStyle  : word;                            { Class style word }
  141.     winClassProc   : longint;       { Window function declared for class }
  142.     winInstance    : thandle;         { Instance handle for window owner }
  143.     winHWndParent  : hwnd;                        { Parent window handle }
  144.     winTitle       : array[0..titleMax] of char;          { Window title }
  145.     winControlID   : word;                   { Control ID or menu handle }
  146.     winWndProc     : longint;    { Window function, usually = class fun. }
  147.     winStyle       : longint;     { Style doubleword for window (WS_...) }
  148.     winWindowRect  : trect;         { Window rectangle (screen-relative) }
  149.     winClientRect  : trect;       { Client rectangle within window rect. }
  150.   end;
  151.   CsrMsg = record
  152.     csBar,                  { which scroll bar this key is equivalent to }
  153.     csMsg : byte;                      { the scroll message for this key }
  154.   end;
  155.  
  156. const
  157. {
  158.   The CsrScroll array is used for implementing keyboard scrolling.  By
  159.   looking up the keystroke in this array, we get the equivalent scroll
  160.   bar message.
  161. }
  162.   CsrScroll : array[0..7] of CsrMsg = (
  163.     (csBar : sb_vert; csMsg : sb_pageup),       { vk_prior (pgup)        }
  164.     (csBar : sb_vert; csMsg : sb_pagedown),     { vk_next  (pgdn)        }
  165.     (csBar : sb_vert; csMsg : sb_bottom),       { vk_end   (end)         }
  166.     (csBar : sb_vert; csMsg : sb_top),          { vk_home  (home)        }
  167.     (csBar : sb_horz; csMsg : sb_lineup),       { vk_left  (left arrow)  }
  168.     (csBar : sb_vert; csMsg : sb_lineup),       { vk_up    (up arrow)    }
  169.     (csBar : sb_horz; csMsg : sb_linedown),     { vk_right (right arrow) }
  170.     (csBar : sb_vert; csMsg : sb_linedown));    { vk_down  (down arrow)  }
  171.  
  172.   MaxWinNum = 64000 div sizeof(Info);       { determined at compile time }
  173.  
  174. type
  175.   InfoArray = array[0..MaxWinNum] of Info;
  176.  
  177. var
  178.   hInst             : thandle;                     { Our instance handle }
  179.   hInfo             : thandle;             { Global handle to INFO array }
  180.   pInfo             : ^InfoArray;{ Far pointer to INFO, when locked down }
  181.   InfoIndex         : integer;                   { index into INFO array }
  182.   nWindows          : integer;       { Total number of windows in system }
  183.   dwInfoSize        : longint;      { Size of entire INFO array in bytes }
  184.   lpprocCountWindow,                      { ProcInstance for CountWindow }
  185.   lpprocSpyOnWindow : tfarproc;           { ProcInstance for SpyOnWindow }
  186.   nCharSizeX,                           { Width of a character in pixels }
  187.   nCharSizeY,                          { Height of a character in pixels }
  188.   nExtLeading,                   { # pixels vertical space between chars }
  189.   nPaintX,                            { For Paint function: X coordinate }
  190.   nPaintY           : integer;        { For Paint function: Y coordinate }
  191.   hdcPaint          : HDC;       { For Paint function: hDC to paint into }
  192.   szClass           : array[0..10] of char;      { Our window class name }
  193.   szTitle           : array[0..40] of char;           { Our window title }
  194.  
  195. {------------------------------------------------------------------------}
  196.  
  197. function CountWindow(hWin     : hwnd;                    { Window handle }
  198.                      TopLevel : longint) : bool; export;
  199. {
  200.   Enumeration function to count the number of windows in the system.
  201.   Called once for each window, via EnumWindows and recursively via
  202.   EnumChildWindows.  The TopLevel parameter tells us which kind of
  203.   call it is. 1=top level window, 0=child window.
  204. }
  205.   begin
  206.     { Count the window }
  207.     inc(dwInfoSize,sizeof(Info));
  208.     inc(nWindows);
  209.  
  210.     { If this is a top level window (or popup), count its children }
  211.     if bool(TopLevel) then
  212.       EnumChildWindows(hWin,lpprocCountWindow,0);
  213.     CountWindow := true;                  { TRUE to continue enumeration }
  214.   end { CountWindow };
  215.  
  216. function DoScrollMsg(hWin   : hwnd;            { Window handle to scroll }
  217.                      nbar   : integer;              { SB_HORZ or SB_VERT }
  218.                      wCode  : word;            { Scroll bar message code }
  219.                      nThumb : integer) : integer;       { Thumb position }
  220. {
  221.   Process a scroll bar message.  Calculates the distance to scroll based
  222.   on the scroll bar range and the message code.  Limits the scroll to the
  223.   actual range of the scroll bar.  Sets the new scroll bar thumb position
  224.   and scrolls the window by the necessary amount.  Note that the scroll
  225.   bar ranges are set in terms of number of characters, while the window
  226.   scrolling is done by a number of pixels.  Returns the distance scrolled
  227.   in chars.
  228. }
  229.   var
  230.     XAmount,
  231.     YAmount,
  232.     nOld,                                 { Previous scroll bar position }
  233.     nDiff,                              { Amount to change scroll bar by }
  234.     nMin,                            { Minimum value of scroll bar range }
  235.     nMax,                            { Maximum value of scroll bar range }
  236.     nPageSize : integer;              { Size of our window in characters }
  237.     rect      : trect;                 { Client rectangle for our window }
  238.   begin
  239.     DoScrollMsg := 0;
  240.  
  241.     { Get old scroll position and scroll range }
  242.     nOld := GetScrollPos(hWin,nBar);
  243.     GetScrollRange(hWin,nBar,nMin,nMax);
  244.  
  245.     { Quit if there is nowhere to scroll to (see SetScrollBars) }
  246.     if nMax = maxint then exit;
  247.  
  248.     { Calculate page size, horizontal or vertical as needed }
  249.     GetClientRect(hWin,rect);
  250.     if nBar = sb_horz then
  251.       nPageSize := (rect.right - rect.left) div nCharSizeX
  252.     else
  253.       nPageSize := (rect.bottom - rect.top) div nCharSizeY;
  254.  
  255.     { Select the amount to scroll by, based on the scroll message }
  256.     case wCode of
  257.       sb_lineup        : nDiff := -1;
  258.       sb_linedown      : nDiff := 1;
  259.       sb_pageup        : nDiff := -nPageSize;
  260.       sb_pagedown      : nDiff := nPageSize;
  261.       sb_thumbposition : nDiff := nThumb - nOld;
  262.       sb_top           : ndiff := -30000;       { kludgey, but effective }
  263.       sb_Bottom        : nDiff := 30000;
  264.     else
  265.       exit;
  266.     end;
  267.  
  268.     { Limit scroll destination to nMin..nMax }
  269.     if nDiff < nMin - nOld then nDiff := nMin - nOld;
  270.     if nDiff > nMax - nOld then nDiff := nMax - nOld;
  271.     if nDiff = 0 then exit;            { Return if net effect is nothing }
  272.  
  273.     { OK, now we can set the new position and scroll the window }
  274.     SetScrollPos(hWin,nBar,nOld + nDiff,true);
  275.  
  276.     if nBar = sb_horz then
  277.       begin
  278.         XAmount := -nDiff * nCharSizeX;
  279.         YAmount := 0;
  280.       end
  281.     else
  282.       begin
  283.         XAmount := 0;
  284.         YAmount := -nDiff * nCharSizeY;
  285.       end;
  286.  
  287.     ScrollWindow(hWin,XAmount,YAmount,nil,nil);
  288.  
  289.     { Force an immediate update for cleaner appearance }
  290.     UpdateWindow(hWin);
  291.  
  292.     DoScrollMsg := nDiff;
  293.   end { DoScrollMsg };
  294.  
  295. procedure HomeScrollBars(hWin   : hwnd;                  { Window handle }
  296.                          Redraw : bool);
  297. {
  298.   Set both scroll bars to the home position (0).  Redraw is TRUE if scroll
  299.   bars should be redrawn.
  300. }
  301.   begin
  302.     SetScrollPos(hWin,sb_horz,0,Redraw);
  303.     SetScrollPos(hWin,sb_vert,0,Redraw);
  304.   end { HomeScrollBars };
  305.  
  306. procedure SetScrollBar1(hWin : hwnd;                     { Window handle }
  307.                         SBar,     { Which scroll bar, SB_HORZ or SB_VERT }
  308.                         Max  : integer);     { Value to set max range to }
  309. {
  310.   Set one scroll bar's maximum range.  We always set the minimum to zero,
  311.   although Windows allows other values.  There is one case we handle
  312.   specially.  If you set a scroll bar range to minimum==maximum (maximum =
  313.   zero for us), Windows does not actually set the range, but instead turns
  314.   off the scroll bar completely, changing the window style by turning off
  315.   the WS_HSCROLL or WS_VSCROLL bit.  For example, this is how the MS-DOS
  316.   Executive makes its scroll bars appear and disappear.  This behavior is
  317.   fine if you take it into account in your programming in two ways.
  318.   First, whenever you do a GetScrollRange you must first check the window
  319.   style to see if that scroll bar still exists, because you willnot* get
  320.   the correct answer from GetScrollRange if it has been removed.  Second,
  321.   you must be prepared to get some extra WM_SIZE messages, because your
  322.   client area changes size when the scroll bars appear and disappear.
  323.   This can cause some sloppy looking screen painting.  We take a different
  324.   approach, always keeping the scroll bars visible.  If the scroll bar
  325.   range needs to be set to zero, instead we set it to MAXINT so the bar
  326.   remains visible.  Then, in DoScrollMessage we check for this case and
  327.   return without scrolling.
  328. }
  329.   var
  330.     OldMin,                          { Previous minimum value (always 0) }
  331.     OldMax : integer;                           { Previous maximum value }
  332.   begin
  333.     { Check for a negative or zero range and set our special case flag.
  334.       Also, set the thumb position to zero in this case. }
  335.     if Max <= 0 then begin
  336.       Max := maxint;
  337.       DoScrollMsg(hWin,SBar,sb_thumbposition,0);
  338.     end;
  339.  
  340.     { Find out the previous range, and set it if it has changed }
  341.     GetScrollRange(hWin,SBar,OldMin,OldMax);
  342.     if Max <> OldMax then
  343.       SetScrollRange(hWin,SBar,0,Max,true);
  344.   end { SetScrollBar1 };
  345.  
  346. procedure SetScrollBars(hWin : hwnd);                    { Window handle }
  347. {
  348.   Set horizontal and vertical scroll bars, based on the window size and
  349.   the number of INFO entries.  The scroll bar ranges are set to give a
  350.   total width of WINDOW_WIDTH and a total height equal to the number of
  351.   lines of information available.  For example, if there are 130 lines of
  352.   information and the window height is 10 characters, the vertical scroll
  353.   range is set to 120 (130-10).  This lets you scroll through everything
  354.   and still have a full window of information at the bottom.  (Unlike,
  355.   say, Windows Write, where if you scroll to the bottom you have a blank
  356.   screen.)
  357. }
  358.   var
  359.     Rect : trect;                        { The window's client rectangle }
  360.   begin
  361.     GetClientRect(hWin,Rect);
  362.     SetScrollBar1(hWin,sb_horz,WindowWidth - (rect.right div nCharSizeX));
  363.     SetScrollBar1(hWin,sb_vert,
  364.                   (nWindows * LinesPerWin) - (rect.bottom div nCharSizeY));
  365.   end { SetScrollBars };
  366.  
  367. function SpyOnAllWindows(hWin : hwnd) : bool;            { Window handle }
  368. {
  369.   Loop through all windows in the system and gather up information for the
  370.   INFO array for each.  Use the EnumWindows and EnumChildWindows functions
  371.   to loop through them.  We actually loop through them twice: first, to
  372.   simply count them so we can allocate global memory for the INFO array,
  373.   and again to actually fill in the array.  After gathering up the
  374.   information, we invalidate our window, which will cause a WM_PAINT
  375.   message to be posted, so it will get repainted.
  376. }
  377.   begin
  378.     { Calculate the number of windows and amount of memory needed }
  379.     nWindows := 0;
  380.     dwInfoSize := 0;
  381.     EnumWindows(lpprocCountWindow,1);
  382.  
  383.     { Allocate the memory, complain if we couldn't get it }
  384.     hInfo := GlobalReAlloc(hInfo,dwInfoSize,gmem_moveable);
  385.     if hInfo = 0 then begin
  386.       nWindows := 0;
  387.       dwInfoSize := 0;
  388.       GlobalFree(hInfo);
  389.       MessageBox(GetActiveWindow,'Insufficient memory!!',nil,
  390.                  mb_ok or mb_iconhand);
  391.       PostQuitMessage(0);
  392.       SpyOnAllWindows := false;
  393.       exit;
  394.     end;
  395.  
  396.     { Lock down the memory and fill in the information, then unlock it }
  397.     pInfo := GlobalLock(hInfo);
  398.     InfoIndex := 0;
  399.     EnumWindows(lpprocSpyOnWindow,1);
  400.     GlobalUnlock(hInfo);
  401.  
  402.     { Set the scroll bars based on new window count, repaint our window }
  403.     SetScrollBars(hWin);
  404.     HomeScrollBars(hWin,true);
  405.     InvalidateRect(hWin,nil,true);
  406.  
  407.     SpyOnAllWindows := true;
  408.   end { SpyOnAllWindows };
  409.  
  410. procedure Paint(     szFormat : pchar;                   { Format string }
  411.                  var Args);                                 { parameters }
  412. {
  413.   Format and paint a line of text.  szFormat and Args are just as in a
  414.   sprintf() call (Args is a variable number of arguments).  The global
  415.   variables nPaintX and nPaintY tell where to paint the line.  We
  416.   increment nPaintY to the next line after painting.
  417. }
  418.   var
  419.     nLength : integer;                      { Length of formatted string }
  420.     Buf     : array[0..160] of char;      { Buffer to format string into }
  421.   begin
  422.     nLength := wvsprintf(Buf,szFormat,Args);
  423.     TextOut(hdcPaint,nPaintX,nPaintY+nExtLeading,Buf,nLength);
  424.     inc(nPaintY,nCharSizeY);
  425.   end { Paint };
  426.  
  427. procedure PaintWindow(hWin : hwnd);             { Window handle to paint }
  428. {
  429.   Paints our window or any portion of it that needs painting.
  430.   The BeginPaint call sets up a structure that tells us what rectangle of
  431.   the window to paint, along with other information for the painting
  432.   process.  First, erase the background area if necessary.  Then,
  433.   calculate the index into the INFO array to start with, based on the
  434.   painting rectangle and the scroll bar position, and lock down the INFO.
  435.   Finally, loop through the INFO array, painting the text for each entry.
  436.   Quit when we run out of entries or hit the bottom of the paint
  437.   rectangle.
  438. }
  439.   type
  440.     TOneLiner = record                        { parameters for wvsprintf }
  441.       v1 : pchar;
  442.       v2 : word;
  443.       v3 : pchar;
  444.       v4,
  445.       v5,
  446.       v6,
  447.       v7 : integer;
  448.       v8 : pchar;
  449.     end;
  450.     THandleParam = record
  451.       v1 : pchar;
  452.       v2 : hwnd;
  453.     end;
  454.     TWordParam = record
  455.       v1,
  456.       v2,
  457.       v3,
  458.       v4 : word;
  459.     end;
  460.     TIntParam = record
  461.       v1,
  462.       v2,
  463.       v3,
  464.       v4 : integer;
  465.     end;
  466.     TStyleParam = record
  467.       v1 : word;
  468.       v2 : longint;
  469.     end;
  470.   var
  471.     ps              : tpaintstruct; { Paint structure used by BeginPaint }
  472.     rgbOldTextColor,             { Old text color (so we can restore it) }
  473.     rgbOldBkColor   : longint;                    { Old background color }
  474.     nWin,                                        { Index into INFO array }
  475.     X,                                { X position for paint calculation }
  476.     Y               : integer;        { Y position for paint calculation }
  477.     pTypeName       : pchar;           { Pointer to "Child", etc. string }
  478.     ExpandFactor    : integer;
  479.     OneLiner        : TOneLiner;
  480.     HandleParam     : THandleParam;
  481.     WordParam       : TWordParam;
  482.     IntParam        : TIntParam;
  483.     StyleParam      : TStyleParam;
  484.     SaveFont        : hfont;                 { Saved device context font }
  485.   begin
  486.     { Tell Windows we're painting, set up the paint structure. }
  487.     BeginPaint(hWin,ps);
  488.  
  489.     { Store display context in global for Paint function }
  490.     hdcPaint := ps.hdc;
  491.  
  492.     { Get our font }
  493.     SaveFont := SelectObject(ps.hdc,GetStockObject(SpyFont));
  494.  
  495.     { Set up proper background and text colors and save old values }
  496.     rgbOldBkColor := SetBkColor(ps.hdc,GetSysColor(color_window));
  497.     rgbOldTextColor := SetTextColor(ps.hdc,GetSysColor(color_windowtext));
  498.  
  499.     { Calculate horizontal paint position based on scroll bar position }
  500.     X := (1 - GetScrollPos(hWin,sb_horz)) * nCharSizeX;
  501.  
  502.     { Calculate index into INFO array and vertical paint position, based
  503.       on scroll bar position and top of painting rectangle }
  504.     Y := GetScrollPos(hWin,sb_vert);
  505.     nWin := (ps.rcPaint.top div nCharSizeY + Y) div LinesPerWin;
  506.     nPaintY := (nWin * LinesPerWin - Y) * nCharSizeY;
  507.  
  508.     { Lock down INFO array.  nWin is index to first entry to paint }
  509.     pInfo := GlobalLock(hInfo);
  510.  
  511.     { Loop through INFO entries, painting each one until we run out of
  512.       entries or until we are past the bottom of the paint rectangle.  We
  513.       don't worry much about painting outside the rectangle - Windows will
  514.       clip for us. }
  515.  
  516.     while (nWin < nWindows) and (nPaintY < ps.rcPaint.bottom) do begin
  517.       { Set X position and indent child windows, also set up pTypeName }
  518.       nPaintX := X;
  519.       if bool(pInfo^[nWin].winStyle and ws_child) then
  520.         begin
  521.           if bExpand then
  522.             ExpandFactor := 4
  523.           else
  524.             ExpandFactor := 2;
  525.           inc(nPaintX,nCharSizeX * Expandfactor);
  526.           pTypeName := 'Child';
  527.         end
  528.       else
  529.         if bool(pInfo^[nWin].winStyle and ws_iconic) then
  530.           pTypeName := 'Icon'
  531.         else
  532.           if bool(pInfo^[nWin].winStyle and ws_popup) then
  533.             pTypeName := 'Popup'
  534.           else
  535.             pTypeName := 'Top Level';
  536.  
  537.       if not bExpand then
  538.         begin
  539.           { Paint the one-liner }
  540.           with OneLiner do begin
  541.             v1 := pTypeName;
  542.             v2 := pInfo^[nWin].winHWnd;
  543.             v3 := pInfo^[nWin].winClass;
  544.             v4 := pInfo^[nWin].winWindowRect.left;
  545.             v5 := pInfo^[nWin].winWindowRect.top;
  546.             v6 := pInfo^[nWin].winWindowRect.right;
  547.             v7 := pInfo^[nWin].winWindowRect.bottom;
  548.             v8 := pInfo^[nWin].winTitle;
  549.           end;
  550.           Paint('%s window %04X {%s} (%d,%d;%d,%d) "%s"',OneLiner);
  551.         end
  552.       else
  553.         begin
  554.           { Paint the expanded form, first the window handle }
  555.           with HandleParam do begin
  556.             v1 := pTypeName;
  557.             v2 := pInfo^[nWin].winHWnd;
  558.           end;
  559.           Paint('%s window handle: %04X',HandleParam);
  560.  
  561.           { Paint the rest of the info, indented two spaces farther over }
  562.           inc(nPaintX,nCharSizeX * 2);
  563.  
  564.           with HandleParam do v1 := pInfo^[nWin].winClass;
  565.           Paint('Class name: %s',HandleParam);
  566.           with HandleParam do v1 := pInfo^[nWin].winTitle;
  567.           Paint('Window title: %s',HandleParam);
  568.           Paint('Parent window handle: %04X',pInfo^[nWin].winHWndParent);
  569.           with WordParam do begin
  570.             v1 := hiword(pInfo^[nWin].WinClassProc);
  571.             v2 := loword(pInfo^[nWin].WinClassProc);
  572.             v3 := hiword(pInfo^[nWin].WinWndProc);
  573.             v4 := loword(pInfo^[nWin].WInWndProc);
  574.           end;
  575.           Paint('Class function, Window function: %04X:%04X, %04X:%04X',WordParam);
  576.           with WordParam do begin
  577.             v1 := pInfo^[nWin].winClassModule;
  578.             v2 := pInfo^[nWin].winInstance;
  579.           end;
  580.           Paint('Class module handle, Window instance handle: %04X, %04X',WordParam);
  581.           with WordParam do begin
  582.             v1 := pInfo^[nWin].winClsExtra;
  583.             v2 := pInfo^[nWin].winWndExtra;
  584.           end;
  585.           Paint('Class extra alloc, Window extra alloc: %d, %d',WordParam);
  586.           with StyleParam do begin
  587.             v1 := pInfo^[nWin].winClassStyle;
  588.             v2 := pInfo^[nWin].winStyle;
  589.           end;
  590.           Paint('Class style, Window style: %04X, %08lX',StyleParam);
  591.           if bool(pInfo^[nWin].winStyle and ws_child) then
  592.             Paint('Control ID: %d',pInfo^[nWin].winControlID)
  593.           else
  594.             Paint('Menu handle: %04X',pInfo^[nWin].winControlID);
  595.           with WordParam do begin
  596.             v1 := pInfo^[nWin].winBkgdBrush;
  597.             v2 := pInfo^[nWin].winCursor;
  598.             v3 := pInfo^[nWin].winIcon;
  599.           end;
  600.           Paint('Brush, Cursor, Icon handles: %04X, %04X, %04X',WordParam);
  601.           with IntParam do begin
  602.             v1 := pInfo^[nWin].winWindowRect.left;
  603.             v2 := pInfo^[nWin].winWindowRect.top;
  604.             v3 := pInfo^[nWin].winWindowRect.right;
  605.             v4 := pInfo^[nWin].winWindowRect.bottom;
  606.           end;
  607.           Paint('Window rectangle: Left=%4d, Top=%4d, Right=%4d, Bottom=%4d',IntParam);
  608.           with IntParam do begin
  609.             v1 := pInfo^[nWin].winClientRect.left;
  610.             v2 := pInfo^[nWin].winClientRect.top;
  611.             v3 := pInfo^[nWin].winClientRect.right;
  612.             v4 := pInfo^[nWin].winClientRect.bottom;
  613.           end;
  614.           Paint('Client rectangle: Left=%4d, Top=%4d, Right=%4d, Bottom=%4d',IntParam);
  615.  
  616.           { Make a blank line - it's already erased so just increment Y }
  617.           inc(nPaintY,nCharSizeY);
  618.         end;
  619.  
  620.       { Increment to next INFO entry }
  621.       inc(nWin);
  622.     end; { while }
  623.  
  624.     { Unlock the INFO array }
  625.     GlobalUnlock(hInfo);
  626.  
  627.     { Restore old colors }
  628.     SetBkColor(ps.hdc,rgbOldBkColor);
  629.     SetTextColor(ps.hdc,rgbOldTextColor);
  630.  
  631.     { Restore original font }
  632.     SelectObject(ps.hdc,SaveFont);
  633.  
  634.     { Tell Windows we're done painting }
  635.     EndPaint(hWin,ps);
  636.   end { PaintWindow };
  637.  
  638. procedure SetSpyFont(hWin       : hwnd;
  639.                      NewFontCmd : word);
  640. {
  641.   Calculates character height and width for the specified font and stores
  642.   the values in global variables.  Also checks the appropriate item on the
  643.   Font menu.  This routine is new in TPWSpy.
  644. }
  645.   var
  646.     DC       : hdc;
  647.     SaveFont : hfont;
  648.     Metrics  : ttextmetric;                  { Text metrics for our font }
  649.   begin
  650.     DC := GetDC(hWin);
  651.     SaveFont := SelectObject(DC,GetStockObject(FontTable[NewFontCmd]));
  652.     GetTextMetrics(DC,Metrics);
  653.     SelectObject(DC,SaveFont);
  654.     ReleaseDC(hWin,DC);
  655.     nExtLeading := Metrics.tmExternalLeading;
  656.     nCharSizeX := Metrics.tmMaxCharWidth;
  657.     nCharSizeY := Metrics.tmHeight + Metrics.tmExternalLeading;
  658.     CheckMenuItem(GetMenu(hWin),SpyFontCmd,mf_unchecked);
  659.     CheckMenuItem(GetMenu(hWin),NewFontCmd,mf_checked);
  660.     SpyFont := FontTable[NewFontCmd];
  661.     SpyFontCmd := NewFontCmd;
  662.   end { SetSpyFont };
  663.  
  664. procedure ChangeFont(hWin       : hwnd;
  665.                      NewFontCmd : word);
  666. {
  667.   Selects a new stock font and invalidates our window so it will be
  668.   repainted.  This routine is new in TPWSpy.
  669. }
  670.   begin
  671.     SetSpyFont(hWin,NewFontCmd);
  672.     InvalidateRect(hWin,nil,true);
  673.     HomeScrollBars(hWin,false);
  674.     SetScrollBars(hWin);
  675.   end { ChangeFont };
  676.  
  677. function SpyWndProc(hWin   : hwnd;                       { Window handle }
  678.                     Msg,                                { message number }
  679.                     WParam : word;                          { word param }
  680.                     LParam : longint) : longint; export;    { long param }
  681. {
  682.   Window function for our main window.  All messages for our window are
  683.   sent to this function.  For messages that we do not handle here, we call
  684.   DefWindowProc, which performs Windows' default processing for a message.
  685. }
  686.   begin
  687.     SpyWndProc := 0;
  688.     case Msg of
  689.       { Menu command message - process the command }
  690.       wm_Command :
  691.         if LoWord(lParam) = 0 then
  692.           case WParam of
  693.             Cmd_Expand :
  694.               begin
  695.                 bExpand := not bExpand;
  696.                 if bExpand then
  697.                   begin
  698.                     LinesPerWin := MaxLinesPerWin;
  699.                     CheckMenuItem(GetMenu(hWin),Cmd_Expand,mf_checked);
  700.                   end
  701.                 else
  702.                   begin
  703.                     LinesPerWin := 1;
  704.                     CheckMenuItem(GetMenu(hWin),Cmd_Expand,mf_unchecked);
  705.                   end;
  706.                 InvalidateRect(hWin,nil,true);
  707.                 HomeScrollBars(hWin,false);
  708.                 SetScrollBars(hWin);
  709.                 exit;
  710.               end;
  711.             Cmd_Spy :
  712.               begin
  713.                 SpyOnAllWindows(hWin);
  714.                 exit;
  715.               end;
  716.             Cmd_OEMFixedFont..Cmd_SystemFont :
  717.               begin
  718.                 ChangeFont(hWin,WParam);
  719.                 exit;
  720.               end;
  721.           end;
  722.       { Horizontal scroll message - scroll the window }
  723.       wm_HScroll :
  724.         begin
  725.           DoScrollMsg(hWin,sb_horz,WParam,LParam);
  726.           exit;
  727.         end;
  728.       { Vertical scroll message - scroll the window }
  729.       wm_VScroll :
  730.         begin
  731.           DoScrollMsg(hWin,sb_vert,WParam,LParam);
  732.           exit;
  733.         end;
  734.       { Key-down message - handle cursor keys, ignore other keys }
  735.       wm_KeyDown :
  736.         begin
  737.           if (WParam >= vk_MinCursor) and (WParam <= vk_MaxCursor) then
  738.             DoScrollMsg(hWin,
  739.                         CsrScroll[WParam - vk_MinCursor].csBar,
  740.                         CsrScroll[WParam - vk_MinCursor].csMsg,
  741.                         0);
  742.           exit;
  743.         end;
  744.       { Paint message - repaint all or part of our window }
  745.       wm_Paint :
  746.         begin
  747.           PaintWindow(hWin);
  748.           exit;
  749.         end;
  750.       { Size message - recalculate our scroll bars to take the new size
  751.         into account, but only if initialization has been completed.  There
  752.         are several superfluous WM_SIZE messages sent during initialization,
  753.         and it looks ugly if we repaint the scroll bars for all these. }
  754.       wm_Size :
  755.         begin
  756.           if Initted then
  757.             SetScrollBars(hWin);
  758.           exit;
  759.         end;
  760.       { Destroy-window message - time to quit the application }
  761.       wm_Destroy :
  762.         begin
  763.           PostQuitMessage(0);
  764.           exit;
  765.         end;
  766.     end;
  767.     { For all other messages, we pass them on to DefWindowProc }
  768.     SpyWndProc := DefWindowProc(hWin,Msg,WParam,LParam);
  769.   end { SpyWndProc };
  770.  
  771. function SpyOnWindow(hWin     : hwnd;                    { Window handle }
  772.                      TopLevel : longint) : bool; export;
  773. {
  774.   Enumeration function to gather up the information for a single window
  775.   and store it in the INFO array entry pointed to by pInfo.  Increment
  776.   InfoIndex to the next entry afterward.  Called once for each window, via
  777.   EnumWindows for each top level and popup window, and recursively via
  778.   EnumChildWindows for child windows.  The TopLevel parameter tells which
  779.   kind of call it is.  1=top level window, 0=child window.
  780. }
  781.   begin
  782.     { Gather up this window's information }
  783.     pInfo^[InfoIndex].winHWnd := hWin;
  784.     GetClassName(hWin,pInfo^[InfoIndex].winClass,ClassMax);
  785.     pInfo^[InfoIndex].winClass[ClassMax - 1] := #0;
  786.     pInfo^[InfoIndex].winInstance := GetWindowWord(hWin,gww_hinstance);
  787.     pInfo^[InfoIndex].winHWndParent := GetParent(hWin);
  788.     GetWindowText(hWin,pInfo^[InfoIndex].winTitle,TitleMax);
  789.     pInfo^[InfoIndex].winTitle[TitleMax - 1] := #0;
  790.     pInfo^[InfoIndex].winControlID := GetWindowWord(hWin,gww_id);
  791.     pInfo^[InfoIndex].winWndProc := GetWindowLong(hWin,gwl_wndproc);
  792.     pInfo^[InfoIndex].winStyle := GetWindowLong(hWin,gwl_style);
  793.     GetClientRect(hWin,pInfo^[InfoIndex].winClientRect);
  794.     GetWindowRect(hWin,pInfo^[InfoIndex].winWindowRect);
  795.  
  796.     { Gather up class information }
  797.     pInfo^[InfoIndex].winBkgdBrush := GetClassWord(hWin,gcw_HBRBACKGROUND );
  798.     pInfo^[InfoIndex].winCursor := GetClassWord(hWin,gcw_HCURSOR );
  799.     pInfo^[InfoIndex].winIcon := GetClassWord(hWin,gcw_HICON );
  800.     pInfo^[InfoIndex].winClassModule := GetClassWord(hWin,gcw_hmodule);
  801.     pInfo^[InfoIndex].winWndExtra := GetClassWord(hWin,gcw_cbwndextra);
  802.     pInfo^[InfoIndex].winClsExtra := GetClassWord(hWin,gcw_cbclsextra);
  803.     pInfo^[InfoIndex].winClassStyle := GetClassWord(hWin,gcw_style);
  804.     pInfo^[InfoIndex].winClassProc := GetClassLong(hWin,gcl_wndproc);
  805.  
  806.     { Move on to next entry in table }
  807.     inc(InfoIndex);
  808.  
  809.     { If it's a top level window, get its children too }
  810.     if bool(TopLevel) then
  811.       EnumChildWindows(hWin,lpprocSpyOnWindow,0);
  812.     SpyOnWindow := true;                  { TRUE to continue enumeration }
  813.   end { SpyOnWindow };
  814.  
  815. function Initialize(hPrevInst : thandle;
  816.                     Show      : integer) : bool;
  817. {
  818.   Initialize the application.  Some of the initialization is different
  819.   depending on whether this is the first instance or a subsequent
  820.   instance.  For example, we register our window class only in the first
  821.   instance.  Returns TRUE if initialization succeeded, FALSE if failed.
  822.   If hPrevInst is 0, then this is the first instance.  Show is CmsShow
  823.   parameter from WinMain for ShowWindow.
  824.  
  825. }
  826.   var
  827.     Class    : twndclass;            { Class structure for RegisterClass }
  828.     hWin     : hwnd;                                 { Our window handle }
  829.     OurhDC   : HDC;                     { Display context for our window }
  830.     hSysMenu : hmenu;                       { Menu handle of system menu }
  831.     ScreenX,
  832.     ScreenY  : integer;
  833. begin
  834.     ScreenX := GetSystemMetrics(sm_cxscreen);
  835.     ScreenY := GetSystemMetrics(sm_cyscreen);
  836.  
  837.     Initialize := false;
  838.  
  839.     if hPrevInst = 0 then
  840.       begin
  841.         { Initialization for first instance only }
  842.  
  843.         { Load strings from resource file }
  844.         LoadString(hInst,Ids_Class,szClass,sizeof(szClass));
  845.         LoadString(hInst,Ids_Title,szTitle,sizeof(szTitle));
  846.  
  847.         { Register our window class }
  848.         Class.style         := cs_hredraw or cs_vredraw;
  849.         Class.lpfnWndProc   := @SpyWndProc;
  850.         Class.cbClsExtra    := 0;
  851.         Class.cbWndExtra    := 0;
  852.         Class.hInstance     := hInst;
  853.         Class.hIcon         := LoadIcon(hInst,szClass);
  854.         Class.hCursor       := LoadCursor(0,idc_arrow);
  855.         Class.hbrBackground := color_window + 1;
  856.         Class.lpszMenuName  := szClass;
  857.         Class.lpszClassName := szClass;
  858.  
  859.         if not RegisterClass(Class) then
  860.           exit;
  861.       end
  862.         else
  863.           begin
  864.             { Initialization for subsequent instances only }
  865.  
  866.             { Copy data from previous instance }
  867.             GetInstanceData(hPrevInst,ofs(szClass),sizeof(szClass));
  868.             GetInstanceData(hPrevInst,ofs(szTitle),sizeof(szTitle));
  869.           end;
  870.  
  871.     { Initialization for every instance }
  872.  
  873.     { Set up ProcInstance pointers for our Enumerate functions }
  874.     lpprocCountWindow := MakeProcInstance(@CountWindow,hInst);
  875.     lpprocSpyOnWindow := MakeProcInstance(@SpyOnWindow,hInst);
  876.     if (lpprocCountWindow = nil) or (lpprocSpyOnWindow = nil) then
  877.       exit;
  878.  
  879.     { Allocate our INFO array with nothing really allocated yet }
  880.     hInfo := GlobalAlloc(gmem_moveable,1);
  881.     if hInfo = 0 then
  882.       exit;
  883.  
  884.     { Create our tiled window but don't display it yet }
  885.     hWin := CreateWindow(
  886.       szClass,                                              { Class name }
  887.       szTitle,                                            { Window title }
  888.       ws_tiledwindow or ws_hscroll or ws_vscroll,         { Window style }
  889.       (ScreenX * 1) div 20,                            { X: 5% from left }
  890.       (ScreenY * 1) div 10,                            { Y: 10% from top }
  891.       (ScreenX * 9) div 10,                                { nWidth: 90% }
  892.       (ScreenY * 7) div 10,                               { nHeight: 70% }
  893.       0,                              { Parent hWnd (none for top-level) }
  894.       0,                                                   { Menu handle }
  895.       hInst,                                    { Owning instance handle }
  896.       nil);                       {Parameter to pass in WM_CREATE (none) }
  897.  
  898.     { Initialize scroll bars - Windows doesn't do this for us }
  899.     HomeScrollBars(hWin,false);
  900.  
  901.     { Calculate character size for the font we'll be using }
  902.     SetSpyFont(hWin,DefaultFontCmd);
  903.  
  904.     { Make the window visible before grabbing spy info, so it's included }
  905.     ShowWindow(hWin,Show);
  906.  
  907.     { Now grab the spy information }
  908.     if not SpyOnAllWindows(hWin) then
  909.       exit;
  910.  
  911.     { Got all the information, update our display }
  912.     UpdateWindow(hWin);
  913.  
  914.     { Make note that initialization is complete.  This is checked in our
  915.       routine that handles WM_SIZE to eliminate some jitter on startup }
  916.     Initted := true;
  917.     Initialize := true;
  918.   end { Initialize };
  919.  
  920. procedure WinMain;
  921. {
  922.   Application main program.  Not much is done here - we just initialize
  923.   the application, putting up our window, and then we go into the typical
  924.   message dispatching loop that every Windows application has.
  925.   (Translator's note: hInstance, hPrevInst, and CmdShow are declared in
  926.   the SYSTEM unit.)
  927. }
  928.   var
  929.     Msg : tmsg;                                      { Message structure }
  930.   begin
  931.     { Save our instance handle in static variable }
  932.     hInst := hInstance;
  933.  
  934.     { Initialize application, quit if any errors }
  935.     if not Initialize(hPrevInst,CmdShow) then
  936.       halt(255);
  937.  
  938.     { Main message processing loop.  Get each message, then translate
  939.       keyboard messages, and finally dispatch each message to its window
  940.       function. }
  941.     while GetMessage(Msg,0,0,0) do begin
  942.       TranslateMessage(Msg);
  943.       DispatchMessage(Msg);
  944.     end;
  945.  
  946.     halt(msg.wParam);
  947.   end { WinMain };
  948.  
  949. begin
  950.   WinMain;
  951. end.
  952.